/*
 * Copyright 2013 Agustina Martinez-Garcia

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package uk.ljmu.mulgaraifz.httprequests;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.util.HashMap;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
//import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.igfay.jfig.JFig;
import org.igfay.jfig.JFigException;

import uk.ljmu.mulgaraifz.errorhandling.FileException;
import uk.ljmu.mulgaraifz.errorhandling.MulgaraException;
import uk.ljmu.mulgaraifz.model.DublinCore;
import uk.ljmu.mulgaraifz.utilities.GeneralUtilities;

public final class HttpManagement {
	
	private static final HttpManagement INSTANCE;
	static {
		  try {
	            INSTANCE = new HttpManagement();
	        } catch (Exception e) {
	            throw new ExceptionInInitializerError(e);
	        }
	}
    private Logger log = Logger.getLogger(HttpManagement.class);
    /* class properties */
    public static String DEFAULT_PATH = "";
    public static String DEFAULT_NAMESPACE = "test";
    public static String DEFAULT_GRAPH = "rmi://localhost/fedora#test"; // default
									// graph
    public static String SPARQL_SERVICE = "http://localhost:8091/sparql/";
    public static String TQL_SERVICE = "http://localhost:8091/itql/";

    private HashMap<String, String> outputFormats = new HashMap<String, String>();
    {
	outputFormats.put("xml", "xml");
	outputFormats.put("rdfxml", "rdfxml");
	outputFormats.put("json", "json");
	outputFormats.put("n3", "n3");
    }

    /**
     * @return the _OutputFormats
     */
    public HashMap<String, String> get_OutputFormats() {
	return outputFormats;
    }

    private HashMap<String, DublinCore> _mapCollectionInfo = new HashMap<String, DublinCore>();

    public HashMap<String, DublinCore> getCollectionInfo() {
	return this._mapCollectionInfo;
    }

    private HashMap<String, DublinCore> _mapResourceInfo = new HashMap<String, DublinCore>();

    public HashMap<String, DublinCore> getResourceInfo() {
	return this._mapResourceInfo;
    }

    /*
     * Used for printing results
     */
    private Header[] postOpHeaders;

    private static ClientConnectionManager httpConnectionManager;

    private static HttpCustomParameters customParameters;
    
    private static HttpClient httpClient;
    
    private String service;
    /**
     * NOT USED
     */
    private String path;
    /**
     * NOT USED
     */
    private String FILE_NAME;
    /**
     * Mulgara working GRAPH URL
     */
    private String graphURL;

    /**
     * @return the service
     */
    public String getService() {
	return service;
    }

    /**
     * @param service
     *            the service to set
     */
    public void setService(String service) {
	this.service = service;
    }

    public void setPostOpHeaders(Header[] postOpHeaders) {
	this.postOpHeaders = postOpHeaders;
    }

    public Header[] getPostOpHeaders() {
	return postOpHeaders;
    }

    public void setGraphURL(String graphURL) {
	this.graphURL = graphURL;
    }

    public String getGraphURL() {
	return graphURL;
    }

    // CONSTRUCTORS
    //Used this one when Singleton instance
    private HttpManagement() throws MulgaraException {
    	String baseURL;
		try {
			baseURL = JFig.getInstance().getValue("mulgaraServer", "service");
		} catch (JFigException e) {
			log.error("Error:", e);
		    throw new MulgaraException(
			    "Unable to initialise parameters from config file, check error log...");
		}
    	this.service = baseURL;
    	// Initialize HTTP parameters
    	customParameters = new HttpCustomParameters(baseURL);
    	// Initialize connection manager
    	httpConnectionManager = new ThreadSafeClientConnManager(
    		customParameters.getParams(), customParameters
    			.getSchemeRegistry());
    	try {
    	    this.graphURL = JFig.getInstance().getValue("mulgaraServer",
    		    "GRAPH");
    	} catch (JFigException e) {
    	    log.error("Error: ", e);
    	    this.graphURL = HttpManagement.DEFAULT_GRAPH;
    	}
    }
    public static HttpManagement getInstance() {
    	return INSTANCE;
    }
    /*public HttpManagement(String baseURL) {
	this.service = baseURL;
	// Initialize HTTP parameters
	this.customParameters = new HttpCustomParameters(baseURL);
	// Initialize connection manager
	httpConnectionManager = new ThreadSafeClientConnManager(
		this.customParameters.getParams(), this.customParameters
			.getSchemeRegistry());
	try {
	    this.graphURL = JFig.getInstance().getValue("mulgaraServer",
		    "GRAPH");
	} catch (JFigException e) {
	    log.error("Error: ", e);
	    this.graphURL = HttpManagement.DEFAULT_GRAPH;
	}
    }

    public HttpManagement(String baseURL, String graph) {
	this.service = baseURL;
	// Initialize HTTP parameters
	this.customParameters = new HttpCustomParameters(baseURL);
	// Initialize connection manager
	httpConnectionManager = new ThreadSafeClientConnManager(
		this.customParameters.getParams(), this.customParameters
			.getSchemeRegistry());
	this.graphURL = graph;
    }*/

    /**
     * 
     * @return
     */
    public static HttpClient getHttpClient() {

	if (httpClient == null) {
	    httpClient = new DefaultHttpClient(httpConnectionManager,
		    customParameters.getParams());
	}
	return httpClient;
    }

    // METHODS
    /**
     * Perform SPARQL queries to a mulgara instance via HTTP REST service
     * 
     * @param query
     *            string containing the SPARQL query
     * @param format
     *            rdfxml or xml values
     * @return Results array
     * @throws MulgaraException
     */
    public String[] httpSparqlQuery(String query, String format)
	    throws MulgaraException {

	String strBody[];

	try {
	    this.setService(JFig.getInstance().getValue("mulgaraServer",
		    "service"));
	} catch (JFigException e) {
	    log.error("Error:", e);
	    this.setService(HttpManagement.SPARQL_SERVICE);
	}
	
	// Obtain XML document from results
	String auxFormat = outputFormats.get(format);
	if (auxFormat == null) {
	    log.fatal("Wrong format in the query to: " + this.service);
	    return null;
	}
	if (query.equals("")) {
	    log.fatal("httpSparqlQuery, 'Empty query'");
	    return null;
	}

	strBody = this.getRequestEndpoint(query, format);

	if (strBody == null)
	    return null;

	// Check
	//Iterator<?> iterator;

	//if (format.equals(outputFormats.get("rdfxml"))) {
	//    iterator = XmlTraverse.createRDFIterator(strBody[0]);
	//} else if (format.equals(outputFormats.get("xml"))) {
	//    iterator = XmlTraverse.createSparqlXMLIterator(strBody[0]);
	//} else {
	//    log
	//    .fatal("httpSparqlQuery, 'Query format is neither xml or rdfxml.'");
	//    return null;
	//}

	//return iterator;
	return strBody;
    }

   /* *//**
     * Perform SPARQL queries to a mulgara instance via HTTP REST service
     * 
     * @param query
     *            string containing the SPARQL query
     * @param format
     *            of the results of the query
     * @return String object to results of the query
     * @throws MulgaraException
     *//*
    public String httpSparqlQueryN3(String query) throws MulgaraException {

	String strBody[];
	if (query.equals("")) {
	    log.fatal("httpItqlQuery, 'Empty query'");
	    return null;
	}
	strBody = this.getRequestEndpoint(query, outputFormats.get("n3"));

	if (strBody == null)
	    return null;

	return strBody[0];
    }

    *//**
     * Perform SPARQL queries to a mulgara instance via HTTP REST service
     * 
     * @param query
     *            string containing the SPARQL query
     * @return String object to results of the query
     * @throws MulgaraException
     *//*
    public String httpSparqlQueryJson(String query) throws MulgaraException {

	String strBody[];
	if (query.equals("")) {
	    log.fatal("httpQuery JSON output, 'Empty query'");
	    return null;
	}
	strBody = this.getRequestEndpoint(query, outputFormats.get("json"));

	if (strBody == null)
	    return null;

	return strBody[0];
    }
*/
    /**
     * Perform SPARQL queries to a mulgara instance via HTTP REST service
     * 
     * @param query
     *            string containing the TQL query
     * @param format
     *            "rdf" for RDF output format of the query
     * @return Iterator object to results of the query
     * @throws MulgaraException
     */
    public String[] httpItqlQuery(String query, String format)
	    throws MulgaraException {

	String strBody[];
	
	try {
	    this.setService(JFig.getInstance().getValue("mulgaraServer",
		    "tql_service"));
	} catch (JFigException e) {
	    log.error("Error:", e);
	    this.setService(HttpManagement.TQL_SERVICE);
	}
	
	// Obtain XML document from results
	String auxFormat = outputFormats.get(format);
	if (auxFormat == null) {
	    log.fatal("Wrong format in the query to: " + this.service);
	    return null;
	}
	if (query.equals("")) {
	    log.fatal("httpItqlQuery, 'Empty query'");
	    return null;
	}
	strBody = this.getRequestEndpoint(query, format);

	return strBody;
    }

    /**
     * Performs a REST query to the HTTP Sparql Service
     * 
     * @param query
     *            Sparql query string
     * @param format
     *            format of the results of the query
     * 
     * @return Array containing string results of the query and code results
     *         from the operation in case of error
     * @throws MulgaraException
     */
    private String[] getRequestEndpoint(String query, String format)
	    throws MulgaraException {
	String params = "";
	String[] codeResults = new String[2];
	HttpClient httpclient = getHttpClient();
	HttpGet httpget = null;
	try {
	    params = URLEncoder.encode(query, "UTF-8") + "&format=" + format;

	    // Construct get request
	    String serviceUrl = this.service + "?query=" + params;
	    httpget = new HttpGet(serviceUrl);
	    HttpResponse response = null;

	    response = httpclient.execute(httpget);
	    HttpEntity entity = response.getEntity();

	    if (entity != null) {
		codeResults[0] = EntityUtils.toString(entity);
	    } else {
		log.fatal("HttpGet failed: " + response.getStatusLine());
		return null;
	    }
	    // ensure the connection gets released to the manager
	    entity.consumeContent();
	    // return results
	    return codeResults;
	} catch (UnsupportedEncodingException e) {
	    log.fatal("Error while encoding service url: " + e.getMessage());
	    return null;
	} catch (ClientProtocolException e) {
	    log.fatal("Fatal protocol violation: " + e.getMessage());
	    return null;
	} catch (IOException e) {
	    log.fatal("Fatal transport error: " + e.getMessage());
	    throw new MulgaraException(e.getMessage());
	} finally {
	    // When HttpClient instance is no longer needed
	    if (httpget != null) {
	    	httpget.abort();
	    }
	}
    }

    /**
     * Performs a REST query to the HTTP Sparql Service
     * 
     * @param instance
     *            httpclient instance
     * @param query
     *            Sparql query string
     * @param format
     *            format of the results xml, rdf, n3
     * @return Array containing string results of the query and code results
     *         from the operation in case of error
     * @throws MulgaraException
     */
    @SuppressWarnings("unused")
	private String[] getRequestEndpoint(HttpClient instance, String query,
	    String format) throws MulgaraException {
	String[] codeResults = new String[2];
	HttpResponse response = null;
	HttpGet httpget = null;

	HttpClient httpclient = instance;

	try {
	    // Construct get request
	    String serviceUrl = this.service + "?query="
		    + URLEncoder.encode(query, "UTF-8");
	    serviceUrl += "&format=" + format;
	    httpget = new HttpGet(serviceUrl);

	    response = httpclient.execute(httpget);
	    HttpEntity entity = response.getEntity();

	    if (entity != null) {
		codeResults[0] = EntityUtils.toString(entity);
	    } else {
		System.err.println("HttpGet failed: "
			+ response.getStatusLine());
		log.fatal("HttpGet failed: " + response.getStatusLine());
		return null;
	    }
	    // ensure the connection gets released to the manager
	    entity.consumeContent();
	    return codeResults;
	} catch (UnsupportedEncodingException ex) {
	    log.fatal("Fatal protocol violation: " + ex.getMessage());
	    return null;
	} catch (ClientProtocolException e) {
	    log.fatal("Fatal protocol violation: " + e.getMessage());
	    return null;
	} catch (IOException e) {
	    log.fatal("Fatal transport error: " + e.getMessage());
	    throw new MulgaraException(e.getMessage());
	} catch (NullPointerException ex) {
	    log.fatal("Fatal transport error: " + ex.getMessage());
	    throw new MulgaraException(ex.getMessage());
	} finally {
	    if (httpget != null)
		// When HttpClient instance is no longer needed,
		httpget.abort();
	}
    }

    /**
     * Performs a REST query to the HTTP Sparql Service
     * 
     * @param query
     *            Sparql query string
     * @param format
     *            format of the results of the query xml, rdf, n3
     * @return Array containing string results of the query and code results
     *         from the operation in case of error
     */
    @SuppressWarnings("unused")
	private String[] getRequestEndpoint2file(String query, String format) {
	String params = "";
	String[] codeResults = new String[2];
	HttpGet httpget = null;
	InputStream instream = null;

	try {

	    params = URLEncoder.encode(query, "UTF-8") + "&format=" + format;

	    // Create an instance of HttpClient.
	    HttpClient client = getHttpClient();
	    // Create a method instance.
	    httpget = new HttpGet(SPARQL_SERVICE + "?query=" + params);

	    // Execute the method.
	    HttpResponse response = client.execute(httpget);

	    HttpEntity entity = response.getEntity();
	    if (entity == null) {
		System.err
			.println("Method failed: " + response.getStatusLine());
		log.fatal("Method failed: " + response.getStatusLine());
		return null;
	    }

	    // Take care of big results in this String parsing of results
	    // REVISE
	    // byte[] responseBytes = method.getResponseBody();
	    // strBody = new String(responseBytes);
	    // codeResults[0] = strBody;
	    /*
	     * Call method.getResponseBodyAsStream() and read the entire
	     * contents of the stream then call stream.close(). This method is
	     * best if it is possible for a lot of data to be received as it can
	     * be buffered to a file or processed as it is read. Be sure to
	     * always read the entirety of the data and call close on the
	     * stream.
	     */
	    if (entity != null) {
	    instream = entity.getContent();
		    try {
			    BufferedReader buf = new BufferedReader(new InputStreamReader(
				    instream));
		
			    String fileName;
			    fileName = GeneralUtilities.save2file(buf, this.path + FILE_NAME);
			    if (codeResults[0] == null)
				codeResults[0] = "OK";
			    codeResults[1] = fileName;
		    } finally {
		    	instream.close();
		    }
	    }

	    return codeResults;
	} catch (IOException e) {
	    log.fatal("Fatal transport error: " + e.getMessage());
	    return null;
	} catch (FileException e) {
	    log.error("Error: ", e);
	    return null;
	} finally {
	    if (httpget != null)
		httpget.abort();
	}
    }

    /**
     * Method to call Mulgara HTTP Sparql endpoint to load RDF files
     * 
     * @param requestContent
     * @return true if request has been successful
     * @throws MulgaraException
     */
    public boolean httpPostMulgaraRequest(Object requestContent)
	    throws MulgaraException {
	try {
	    this.post(this.service, requestContent, this.graphURL);
	} catch (Exception e) {
	    log.fatal("In HttpPostMulgaraRequest: " + e.getMessage());
	    throw new MulgaraException(e.getMessage());
	}
	return true;
    }

    /**
     * Method to call Mulgara HTTP Sparql endpoint to load RDF files
     * 
     * @param requestContent
     *            contents to be uplaoded
     * @param graphName
     *            graph to allocate the rdf contents
     * @return true if request has been successful
     */
    public boolean httpPostMulgaraRequest(Object requestContent,
	    String graphName) {
	HttpResponse response;

	this.setGraphURL(graphName);
	try {
	    response = this.post(this.service, requestContent, graphName);
	    if (response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT)
		return false;
	} catch (Exception e) {
	    log.fatal("In HttpPostMulgaraRequest: " + e.getMessage());
	}
	return true;
    }

    /**
     * 
     * Http post method to load files into the triplestore
     * 
     * @param serviceURL
     *            url of the sparql service
     * @param requestContent
     *            contents to be uploaded
     * @param graph
     *            graph where the contents will be uploaded
     * @return Httpresponse object with the results of the post operation
     * @throws MulgaraException
     */
    public HttpResponse post(String serviceURL, Object requestContent,
	    String graph) throws MulgaraException {
	HttpClient httpclient = getHttpClient();
	HttpRequestBase httpMethod = null;
	HttpResponse response = null;

	try {
	    httpMethod = new HttpPost(serviceURL);

	    if (requestContent instanceof File) {
		FileBody bin = new FileBody((File) requestContent);
		MultipartEntity reqEntity = new MultipartEntity();
		reqEntity.addPart("default-graph-uri", new StringBody(graph));
		reqEntity.addPart("file", bin);
		((HttpEntityEnclosingRequest) httpMethod).setEntity(reqEntity);
	    } else if (requestContent instanceof String) {
		if (requestContent != "") {
		    MultipartEntity reqEntity = new MultipartEntity();
		    reqEntity.addPart("default-graph-uri",
			    new StringBody(graph));
		    reqEntity.addPart("file", new StringBody(
			    (String) requestContent));
		    ((HttpEntityEnclosingRequest) httpMethod)
			    .setEntity(reqEntity);
		}
	    }

	    response = httpclient.execute(httpMethod);
	    HttpEntity resEntity = response.getEntity();
	    StringBuilder message = new StringBuilder();

	    // Check status line
	    if (response.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
		// We receive HTTP 400 although the graph is created
		log.debug("Graph created");
	    } else {
		log.debug(response.getStatusLine().toString());
		throw new MulgaraException(response.getStatusLine().toString());
	    }
	    if (resEntity != null) {
		resEntity.consumeContent();
	    }
	    Header[] headers = response.getAllHeaders();
	    // For printing graph results
	    this.setPostOpHeaders(headers);
	    message.delete(0, message.length());
	    message.append(response.getStatusLine().toString());
	    for (Header he : headers) {
		message.append(he.getName());
		message.append(": ");
		message.append(he.getValue());
		message.append("\n");
	    }
	    log.debug(message.toString());

	    return response;
	} catch (IOException e) {
	    log.error("Error: ", e);
	    throw new MulgaraException("In post method: " + e.getMessage());
	} finally {
	    if (httpMethod != null) {
		httpMethod.abort();
	    }
	}
    }

    public void finaliseConnections() {
    	httpConnectionManager.shutdown();
    }
    
    public static void destroyConnectionManager() {
    	httpClient.getConnectionManager().shutdown();
    }
}
